using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Microsoft.Practices.Unity;
using MiniStock.Entities.Common;
using MiniStock.Entities.Common.Events;
using MiniStock.Infrastructure.Configurator;
using MiniStock.Infrastructure.Queue;

namespace MiniStock.Infrastructure
{
    public abstract class EventQueueWorkerBase : WorkerBase
    {
        public List<QueuedEventWorkerBase> Workers = null;

        [Dependency]
        public IEventQueue InputEventQueue { get; set; }

        [Dependency]
        public IEventQueue OutputEventQueue { get; set; }

        public EventBase GetEventFromQueue()
        {
            return InputEventQueue.Dequeue();
        }

        public override void InitWork()
        {
        }

        public override bool DoWork()
        {
            var message = GetEventFromQueue();
            if (message == null) return false;

            try
            {
                Trace.CorrelationManager.ActivityId = message.CorrelatedEventId ?? message.EventId;

                Trace.TraceInformation("{0} Submitting message: {1} (id={2}, entity id={3})",
                                       this.Name,
                                       message.GetType().FullName,
                                       message.EventId,
                                       (message is EntityEventBase ? ((EntityEventBase) message).GetEntityHash() : ""));

                if (message is InitWorker) ProcessInitializationMessage((InitWorker) message);
                else ProcessMessage(message);

                PerformanceMonitoring.IncrementCounter("Events processed", this.Name);
                PerformanceMonitoring.IncrementCounter("Events processed per second", this.Name);

                return true;
            } 
            catch(Exception ex)
            {
                PerformanceMonitoring.IncrementCounter("Events faulted", this.Name);
                PerformanceMonitoring.IncrementCounter("Events faulted per second", this.Name);
                throw;
            }
            finally
            {
                Trace.CorrelationManager.ActivityId = Guid.Empty;
            }
        }

        protected void ProcessMessage(EventBase message)
        {
            if(Workers == null)
                throw new ApplicationException("Processor must be initialized first");

            if (!(message is EntityEventBase))
                return;

            var worker = GetWorkerForMessage((EntityEventBase)message);

            Debug.Assert(worker != null);
            worker.AddEvent(message);
        }

        public void ProcessInitializationMessage(InitWorker message)
        {
            //foreach (var initializer in message.Initializers.Select(i => Type.GetType(i))
            //    .Where(s => s != null)
            //    .Select(i => RootContainer.Resolve(i) as IProcessorPluginEntryPoint)
            //    .Where(s => s != null)
            //    )
            //{
            //    EventBaseExtender.KnownTypes.AddRange(initializer.EventRootTypes);
            //    EntityBaseExtender.KnownTypes.AddRange(initializer.EntityRootTypes);
            //}

            //EntityBaseExtender.InitSerializer();
            //EventBaseExtender.InitSerializer();

            if (!String.IsNullOrEmpty(message.OutQueueName))
            {
                OutputEventQueue.Address = message.OutQueueName;
            }

            if(message.ThreadPoolSize != null)
            {
                InitThreadPool(message.ThreadPoolSize.Value);
            }
        }

        protected void ShutdownWorkers()
        {
            var workers = Workers;
            Workers = null;
            foreach (var w in workers) w.SignalStop();
            foreach (var w in workers) w.Stop();
        }

        public override void CleanupWork()
        {
        }

        protected virtual QueuedEventWorkerBase GetWorkerForMessage(EntityEventBase message)
        {
            if (Workers == null)
                throw new ApplicationException("Processor must be initialized first");

            var result = Workers.OrderBy(i => i.GetQueueSize()).FirstOrDefault();

            return result;
        }

        protected abstract QueuedEventWorkerBase CreateWorker(int index);

        protected virtual void InitThreadPool(int poolSize)
        {
            if (Workers != null)
                ShutdownWorkers();

            var workers = new List<QueuedEventWorkerBase>();
            for(int i=0;i<poolSize;i++)
            {
                var w = CreateWorker(i);
                workers.Add(w);
            }
            workers.ForEach(i => i.SignalStart());
            workers.ForEach(i => i.Start());
            Workers = workers;
        }

        protected override void OnStopped()
        {
            base.OnStopped();

            if(Workers != null)
                ShutdownWorkers();
        }
    }
}